/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// cmd.c -- Quake script command processing module

#include "quakedef.h"

void Cmd_ForwardToServer (void);

BOOL	cmd_wait;

//=============================================================================

/*
============
Cmd_Wait_f

Causes execution of the remainder of the command buffer to be delayed until
next frame.  This allows commands like:
bind g "impulse 5; +attack; wait; -attack; impulse 2"
============
*/
void Cmd_Wait_f (void)
{
	cmd_wait = TRUE;
}

/*
=============================================================================

						COMMAND BUFFER

=============================================================================
*/

sizebuf_t	cmd_text;

/*
============
Cbuf_Init
============
*/
void Cbuf_Init (void)
{
	SZ_Alloc (&cmd_text, 8192);		// space for commands and script files
}


/*
============
Cbuf_AddText

Adds command text at the end of the buffer
============
*/
void Cbuf_AddText (char *text)
{
	int		l;

	l = Q_strlen (text);

	if (cmd_text.cursize + l >= cmd_text.maxsize)
	{
		Con_Printf (PRINT_DEFAULT, "Cbuf_AddText: overflow\n");
		return;
	}

	SZ_Write (&cmd_text, text, Q_strlen (text));
}


/*
============
Cbuf_InsertText

Adds command text immediately after the current command
Adds a \n to the text
FIXME: actually change the command buffer to do less copying
============
*/
void Cbuf_InsertText (char *text)
{
	// copy off any commands still remaining in the exec buffer
	char	*temp = NULL;
	int		templen = cmd_text.cursize;

	if (templen)
	{
		temp = (char *) zoneHeap->Alloc (templen);
		Q_memcpy (temp, cmd_text.data, templen);
		SZ_Clear (&cmd_text);
	}
	else temp = NULL;	// shut up compiler

	// add the entire text of the file
	Cbuf_AddText (text);

	// add the copied off data
	if (templen) SZ_Write (&cmd_text, temp, templen);

	if (temp)
	{
		zoneHeap->Free (temp);
		temp = NULL;
	}
}

/*
============
Cbuf_Execute
============
*/
void Cbuf_Execute (void)
{
	int		i;
	char	line[1024];

	while (cmd_text.cursize)
	{
		// find a \n or; line break
		char *text = (char *) cmd_text.data;
		int quotes = 0;

		for (i = 0; i < cmd_text.cursize; i++)
		{
			if (text[i] == '"')
				quotes++;

			if (!(quotes & 1) &&  text[i] == ';')
				break;	// don't break if inside a quoted string

			if (text[i] == '\n')
				break;
		}

		memcpy (line, text, i);
		line[i] = 0;

		// delete the text from the command buffer and move remaining commands down
		// this is necessary because commands (exec, alias) can insert data at the
		// beginning of the text buffer
		if (i == cmd_text.cursize)
			cmd_text.cursize = 0;
		else
		{
			i++;
			cmd_text.cursize -= i;
			Q_memcpy (text, text + i, cmd_text.cursize);
		}

		// execute the command line
		Cmd_ExecuteString (line, src_command);

		if (cmd_wait)
		{
			// skip out while text still remains in buffer, leaving it for next frame
			cmd_wait = FALSE;
			break;
		}
	}
}

/*
==============================================================================

						SCRIPT COMMANDS

==============================================================================
*/

/*
===============
Cmd_StuffCmds_f

Adds command line parameters as script statements
Commands lead with a +, and continue until a - or another +
quake +prog jctest.qp +cmd amlev1
quake -nosound +cmd amlev1
===============
*/
void Cmd_StuffCmds_f (void)
{
	int		i, j;
	int		s;
	char	*text, *build, c;

	if (Cmd_Argc () != 1)
	{
		Con_Printf (PRINT_DEFAULT, "stuffcmds : execute command line parameters\n");
		return;
	}

	// build the combined string to parse from
	s = 0;

	for (i = 1; i < com_argc; i++)
	{
		if (!com_argv[i])
			continue;		// NEXTSTEP nulls out -NXHost

		s += Q_strlen (com_argv[i]) + 1;
	}

	if (!s)
		return;

	text = (char *) zoneHeap->Alloc (s + 1);
	text[0] = 0;

	for (i = 1; i < com_argc; i++)
	{
		if (!com_argv[i])
			continue;		// NEXTSTEP nulls out -NXHost

		Q_strcat (text, com_argv[i]);

		if (i != com_argc - 1)
			Q_strcat (text, " ");
	}

	// pull out the commands
	build = (char *) zoneHeap->Alloc (s + 1);
	build[0] = 0;

	for (i = 0; i < s - 1; i++)
	{
		if (text[i] == '+')
		{
			i++;

			for (j = i; (text[j] != '+') && (text[j] != '-') && (text[j] != 0); j++)
				;

			c = text[j];
			text[j] = 0;

			Q_strcat (build, text + i);
			Q_strcat (build, "\n");
			text[j] = c;
			i = j - 1;
		}
	}

	if (build[0])
		Cbuf_InsertText (build);

	zoneHeap->Free (text);
	zoneHeap->Free (build);
}


/*
===============
Cmd_Exec_f
===============
*/
void Cmd_Exec_f (void)
{
	if (Cmd_Argc () != 2)
	{
		Con_Printf (PRINT_DEFAULT, "exec <filename> : execute a script file\n");
		return;
	}
	else
	{
		char *f = (char *) FS_LoadFile (Cmd_Argv (1), qScratch);

		if (!f)
		{
			Con_Printf (PRINT_DEFAULT, "couldn't exec %s\n", Cmd_Argv (1));
			return;
		}

		// text is inserted first so that clearing the temp pool at the end of the
		// screen refresh triggered by Con_Printf won't hose the loaded text
		Cbuf_InsertText (f);

		// printing is done after so that it's screen refresh will clear the temp pool
		Con_Printf (PRINT_DEFAULT, "execing %s\n", Cmd_Argv (1));
	}
}


/*
===============
Cmd_Echo_f

Just prints the rest of the line to the console
===============
*/
void Cmd_Echo_f (void)
{
	int		i;

	for (i = 1; i < Cmd_Argc (); i++)
		Con_Printf (PRINT_DEFAULT, "%s ", Cmd_Argv (i));

	Con_Printf (PRINT_DEFAULT, "\n");
}


/*
=============================================================================

					COMMAND EXECUTION

=============================================================================
*/


#define	MAX_ARGS		80

// renamed these two so that we can search source for them and not get mixed up with the functions
static	int			cmdArgc;
static	char		*cmdArgv[MAX_ARGS];
static	char		*cmd_null_string = "";
static	char		*cmdArgs = NULL;

cmd_source_t	cmd_source;


/*
============
Cmd_Init
============
*/
void Cmd_Init (void)
{
	// register our commands
	Cmd_AddCommand ("stuffcmds", Cmd_StuffCmds_f);
	Cmd_AddCommand ("exec", Cmd_Exec_f);
	Cmd_AddCommand ("echo", Cmd_Echo_f);
	Cmd_AddCommand ("alias", Cmd_Alias_f);
	Cmd_AddCommand ("unalias", Cmd_Unalias_f);
	Cmd_AddCommand ("unaliasall", Cmd_UnaliasAll_f);
	Cmd_AddCommand ("cmd", Cmd_ForwardToServer);
	Cmd_AddCommand ("wait", Cmd_Wait_f);
	Cmd_AddCommand ("seta", Cvar_SetA_f);
	Cmd_AddCommand ("resetall", Cmd_ResetAll_f);
}

/*
============
Cmd_Argc
============
*/
int	Cmd_Argc (void)
{
	return cmdArgc;
}

/*
============
Cmd_Argv
============
*/
char *Cmd_Argv (int arg)
{
	if (arg >= cmdArgc)
		return cmd_null_string;

	return cmdArgv[arg];
}

/*
============
Cmd_Args
============
*/
char *Cmd_Args (void)
{
	return cmdArgs;
}


/*
============
Cmd_TokenizeString

Parses the given string into command line tokens.
============
*/
void Cmd_TokenizeString (char *text)
{
	// the maximum com_token is 1024 so the command buffer will never be larger than this
	// yes, it's 80k, but it prevents heap fragmentation and possible heap corruptions so it's a small tradeoff
	static char argbuf[MAX_ARGS * (1024 + 1)];
	char *currarg = argbuf;

	cmdArgc = 0;
	cmdArgs = NULL;

	for (;;)
	{
		// skip whitespace up to a /n
		while (*text && *text <= ' ' && *text != '\n')
			text++;

		if (*text == '\n')
		{
			// a newline seperates commands in the buffer
			text++;
			break;
		}

		if (!*text) return;
		if (cmdArgc == 1) cmdArgs = text;
		if (!(text = COM_Parse (text))) return;

		if (cmdArgc < MAX_ARGS)
		{
			cmdArgv[cmdArgc] = currarg;
			Q_strcpy (cmdArgv[cmdArgc], com_token);
			currarg += strlen (com_token) + 1;
			cmdArgc++;
		}
	}
}


/*
============
Cmd_ExecuteString

A complete command line has been parsed, so try to execute it
FIXME: lookupnoadd the token to speed search?
============
*/
void Cmd_ExecuteString (char *text, cmd_source_t src)
{
	clistentry_t *found = NULL;

	cmd_source = src;
	Cmd_TokenizeString (text);

	// execute the command line
	if (!Cmd_Argc ())
		return;		// no tokens

	if ((found = Cmd_FindInCompleteList (cmdArgv[0])) != NULL)
	{
		switch (found->type)
		{
		case clist_cmd:
			((cmd_function_t *) found->data)->function ();
			break;

		case clist_cvar:
			Cvar_Command ((cvar_t *) found->data);
			break;

		case clist_alias:
			Cbuf_InsertText (((cmdalias_t *) found->data)->value);
			break;
		}
	}
	else if (!Cvar_Command (NULL))
		Con_Printf (PRINT_DEFAULT, "Unknown command \"%s\"\n", cmdArgv[0]);
}


/*
===================
Cmd_ForwardToServer

Sends the entire command line over to the server
===================
*/
void Cmd_ForwardToServer (void)
{
	if (cls.state != ca_connected)
	{
		Con_Printf (PRINT_DEFAULT, "Can't \"%s\", not connected\n", Cmd_Argv (0));
		return;
	}

	if (cls.demoplayback)
		return;		// not really connected

	MSG_WriteByte (&cls.message, clc_stringcmd);

	if (Q_strcasecmp (Cmd_Argv (0), "cmd") != 0)
	{
		SZ_Print (&cls.message, Cmd_Argv (0));
		SZ_Print (&cls.message, " ");
	}

	if (Cmd_Argc () > 1)
		SZ_Print (&cls.message, Cmd_Args ());
	else SZ_Print (&cls.message, "\n");
}


/*
================
Cmd_CheckParm

Returns the position (1 to argc-1) in the command's argument list
where the given parameter apears, or 0 if not present
================
*/

int Cmd_CheckParm (char *parm)
{
	int i;

	if (!parm)
		Sys_Error ("Cmd_CheckParm: NULL");

	for (i = 1; i < Cmd_Argc (); i++)
		if (!Q_strcasecmp (parm, Cmd_Argv (i)))
			return i;

	return 0;
}


/*
============
Cmd_AddCommand
============
*/
void Cmd_AddCommand (char *cmd_name, xcommand_t function)
{
	cmd_function_t *cmd;

	// fail if the command already exists
	if (Cmd_FindInCompleteList (cmd_name))
	{
		Con_Printf (PRINT_DEVELOPER, "Cmd_AddCommand: %s already defined\n", cmd_name);
		return;
	}

	cmd = (cmd_function_t *) zoneHeap->Alloc (sizeof (cmd_function_t));

	cmd->name = CopyString (cmd_name);
	cmd->function = function;

	Cmd_AddToCompleteList (cmd->name, cmd, clist_cmd);
}


/*
===============
Cmd_Alias_f

Creates a new command that executes a command string (possibly; seperated)
===============
*/

cmdalias_t	*cmd_alias;

void Cmd_Alias_f (void)
{
	cmdalias_t	*a;
	char		cmd[1024];
	int			i, c;
	char		*s;

	if (Cmd_Argc () == 1)
	{
		Con_Printf (PRINT_DEFAULT, "Current alias commands:\n");

		for (a = cmd_alias; a; a = a->next)
			Con_Printf (PRINT_DEFAULT, "%s : %s\n", a->name, a->value);

		return;
	}

	s = Cmd_Argv (1);

	// if the alias allready exists, reuse it
	for (a = cmd_alias; a; a = a->next)
	{
		if (!strcmp (s, a->name))
		{
			zoneHeap->Free (a->value);
			a->value = NULL;
			break;
		}
	}

	if (!a)
	{
		a = (cmdalias_t *) zoneHeap->Alloc (sizeof (cmdalias_t));
		a->name = CopyString (s);

		a->next = cmd_alias;
		cmd_alias = a;

		Cmd_AddToCompleteList (a->name, a, clist_alias);
	}

	// copy the rest of the command line
	cmd[0] = 0;		// start out with a null string
	c = Cmd_Argc ();

	for (i = 2; i < c; i++)
	{
		strcat (cmd, Cmd_Argv (i));

		if (i != c)
			strcat (cmd, " ");
	}

	strcat (cmd, "\n");

	a->value = CopyString (cmd);
}


void Cmd_Unalias_f (void)
{
	cmdalias_t	*a, *prev;

	switch (Cmd_Argc ())
	{
	default:
	case 1:
		Con_Printf (PRINT_SAFE, "unalias <name> : delete alias\n");
		break;

	case 2:
		if (!cmd_alias) return;

		for (prev = a = cmd_alias; a; a = a->next)
		{
			if (!a) break;

			if (!strcmp (Cmd_Argv (1), a->name))
			{
				prev->next = a->next;

				Cmd_RemoveFromCompleteList (a->name);

				zoneHeap->Free (a->value);
				a->value = NULL;

				zoneHeap->Free (a->name);
				a->name = NULL;

				zoneHeap->Free (a);
				a = NULL;

				prev = a;

				return;
			}

			prev = a;
		}

		break;
	}
}


void Cmd_UnaliasAll_f (void)
{
	while (cmd_alias)
	{
		cmdalias_t *blah = cmd_alias->next;

		Cmd_RemoveFromCompleteList (cmd_alias->name);

		zoneHeap->Free (cmd_alias->value);
		cmd_alias->value = NULL;

		zoneHeap->Free (cmd_alias->name);
		cmd_alias->name = NULL;

		zoneHeap->Free (cmd_alias);
		cmd_alias = blah;
	}

	cmd_alias = NULL;
}


void Cmd_ResetAll_f (void)
{
	// if this is a menu option it should also be a cmd
	Cbuf_AddText ("exec default.cfg\n");
}

